package client
{
	import flare.scale.QuantitativeScale;
	import flare.scale.ScaleType;
	import flare.util.Shapes;
	import flare.util.palette.SizePalette;
	import flare.vis.Visualization;
	import flare.vis.controls.ClickControl;
	import flare.vis.controls.DragControl;
	import flare.vis.controls.HoverControl;
	import flare.vis.data.Data;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.data.ScaleBinding;
	import flare.vis.data.render.ArrowType;
	import flare.vis.events.SelectionEvent;
	import flare.vis.operator.OperatorSwitch;
	import flare.vis.operator.encoder.PropertyEncoder;
	import flare.vis.operator.encoder.SizeEncoder;
	import flare.vis.operator.label.Labeler;
	import flare.vis.operator.layout.Layout;
	
	import flash.display.Sprite;
	import flash.geom.Rectangle;
	import flash.text.TextFormat;
	
	import mx.containers.VBox;
	import mx.managers.PopUpManager;
	import mx.preloaders.Preloader;

	/**
	 * Demo showcasing a number of tree and graph layout algorithms.
	 */
	public class CourseGraphLayout extends Sprite
	{
		private var vis:Visualization;
		private var shape:String = null;
		
		private var opt:Object;
		
		private var bounds:Rectangle;
		
		private static var _minEdgeWeightThreshold:uint;
		private static var _maxEdgeWeightThreshold:uint;
		
		private static var _forceDirectedLayout:CustomForceDirectedLayout;
		private static var _chronologicalLayout:CustomChronologicalLayout;
		private static var _currentLayout:Layout;
		
		private var _win:CourseInformationWindow;
		
		public function CourseGraphLayout(graph:Data, expandNodeHandler:Function) {
			_minEdgeWeightThreshold = 0;
			_maxEdgeWeightThreshold = 0;
			name = "Layouts";
			bounds = new Rectangle(0, 30, 770, 650);
			if (graph) {
				init(graph, expandNodeHandler);
			}
		}

		public function init(graph:Data, expandNodeHandler:Function):void
		{
			// create a collection of layout options
			opt = options(bounds.width, bounds.height);
			
			// create the visualization
			if (vis != null) {
				vis.operators.clear();
			}
			vis = new Visualization(graph);
			vis.bounds = bounds;
			// apply layout
			setForceDirectedLayout();
			
			// add labels and set properties
			vis.operators.add(new Labeler("data.id", Data.NODES, new TextFormat("Helvetica", "9", "0x00224466", true), null, Labeler.CHILD));
			vis.operators.add(new PropertyEncoder(opt.edgeProperties, Data.EDGES));
			vis.operators.add(new PropertyEncoder(opt.nodeProperties, Data.NODES));
			
			// create and add a size encoder for the edge lineWidths
			var lineWidthEncoder:SizeEncoder = new SizeEncoder("data.weight", Data.EDGES, new SizePalette(2, 10, false));
			lineWidthEncoder.target = "lineWidth";
			lineWidthEncoder.scale.scaleType = ScaleType.LINEAR;
			vis.operators.add(lineWidthEncoder);
			
			// create and add a size encoder for the edge lineWidths
			var radiusEncoder:SizeEncoder = new SizeEncoder("data.students.length", Data.NODES, new SizePalette(1, 3, false));
			radiusEncoder.scale.scaleType = ScaleType.LINEAR;
			vis.operators.add(radiusEncoder);
			
			// add interaction controls
			vis.controls.add(
				new HoverControl(
					NodeSprite,															// this control aplies to NodeSprites
					HoverControl.MOVE_AND_RETURN,										// by default, move highlighted items to front
					function(e:SelectionEvent):void { e.node.data.hovered = true; },	// rollover handler
					function(e:SelectionEvent):void { e.node.data.hovered = false; }	// rollout handler
				)
			);
			vis.controls.add(
				new HoverControl(
					NodeSprite,
					HoverControl.DONT_MOVE,
					function(e:SelectionEvent):void { 
							_win = CourseInformationWindow(PopUpManager.createPopUp(parent, CourseInformationWindow));
							_win.title = "Node Information";
							_win.x = e.cause.stageX;
							_win.y = e.cause.stageY;
							_win.updateNodeInformation(e.node);
						},	// rollover handler
					function(e:SelectionEvent):void { PopUpManager.removePopUp(_win) }	// rollout handler
				)
			);
			vis.controls.add(new DragControl(NodeSprite));
			vis.controls.add(
				new ClickControl(
					NodeSprite,																			// filter: applies to NodeSprites
					1,																					// numclicks: triggered on single-clicks
					function(e:SelectionEvent):void { e.node.data.selected = !e.node.data.selected; },	// onclick handler
					function(e:SelectionEvent):void { e.node.data.selected = false; }					// onclear handler
				)
			);
			vis.controls.add(
				new ClickControl(
					NodeSprite,																			// filter: applies to NodeSprites
					2,																					// numclicks: triggered on single-clicks
					expandNodeHandler,	// onclick handler
					null
				)
			);
			
			// display the vis
			vis.update();
			addChild(vis);
		}
		
		public function resize():void
		{
			bounds.x += 15; bounds.width -=30;
			bounds.y += 15; bounds.height -=15;
			if (vis) {
				vis.bounds = bounds;
				vis.update();
			}
		}
		
		public function play():void
		{
			if (opt.update) vis.continuousUpdates = true;
		}
		
		public function stop():void
		{
			if (vis != null) {
				vis.continuousUpdates = false;
			}
		}
		
		/**
		 * This method builds a collection of layout operators and node
		 * and edge settings to be applied in the demo.
		 */
		private function options(w:Number, h:Number):Object
		{
			var o:Object = {
				update: true,
				nodeProperties: {
					fillColor:	function(n:NodeSprite):uint {
									if (n.data.selected || n.data.hovered) {
										return 0x88ff0000
									} else {
										return 0x88aaaaaa
									}
								},
					lineColor:	function(n:NodeSprite):uint {
									if (n.data.selected || n.data.hovered) {
										return 0x88ff0000
									} else {
										return 0x88aaaaaa
									}
								},
					buttonMode:	true,
					shape:		Shapes.CIRCLE,
					lineWidth:	1,
					alpha:		1,
					visible:	function(n:NodeSprite):Boolean {
									var index:uint = 0;
									var edgeSprite:EdgeSprite;
									var nodeVisible:Boolean = false;
									if (vis.data.nodes.length == 1) {
										n.size = 2;
										return true;
									}
									if (n.inDegree > 0) {
										while (n.inDegree > 0 && (edgeSprite = n.getInEdge(index)) != null) {
											if (edgeSprite.visible) {
												nodeVisible = true;
												break;
											}
											index++
										}
									}
									index = 0;
									if (!nodeVisible && n.outDegree > 0) {
										while ((edgeSprite = n.getOutEdge(index))!=null) {
											if (edgeSprite.visible) {
												nodeVisible = true;
												break;
											}
											index++
										}
									}
									n.props.particle.visible = nodeVisible;
									return nodeVisible;
								},
					size:		1
				},
				edgeProperties: {
					lineColor:	function(e:EdgeSprite):uint {
									if (e.source.data.selected) {
										return 0x88ff0000
									} else {
										return 0x88aaaaaa
									}
								},
					arrowType:	function(e:EdgeSprite):String {
									if (e.directed) {
										return ArrowType.TRIANGLE;
									} else {
										return ArrowType.NONE;
									}
								},
					alpha:		1,
					visible:	function(e:EdgeSprite):Boolean {
									return e.data.weight >= _minEdgeWeightThreshold && e.data.weight <= _maxEdgeWeightThreshold;
								}
				}
			};
			
			return o;
		}
		
		public function setChronologicalLayout():void {
			if (vis) {
				CustomSimulation.forcesDisabled = true;
				if (_currentLayout) {
					vis.operators.remove(_currentLayout);
				}
				
				// _chronologicalLayout is static!
				if (!_chronologicalLayout) {
					_chronologicalLayout = new CustomChronologicalLayout();
				}
				
				_currentLayout = _chronologicalLayout;
				
				vis.operators.add(_currentLayout);
				
			}
		}
		
		public function setForceDirectedLayout(disableForces:Boolean = false):void {
			if (vis) {
				if (_currentLayout) {
					vis.operators.remove(_currentLayout);
				}
				
				// _forceDirectedLayout is static!
				if (!_forceDirectedLayout) {
					_forceDirectedLayout = new CustomForceDirectedLayout(true);
					_forceDirectedLayout.parameters = {
						"simulation.dragForce.drag": 0.3,
						defaultParticleMass: 3,
						defaultSpringLength: 100,
						defaultSpringTension: 0.5
					};
				}
				
				CustomSimulation.forcesDisabled = disableForces;
				_currentLayout = _forceDirectedLayout;
				
				vis.operators.add(_currentLayout);
			}
		}
		
		public function set maxEdgeWeightThreshold(value:uint):void
		{
			_maxEdgeWeightThreshold = value;
		}
		
		public function set minEdgeWeightThreshold(value:uint):void
		{
			_minEdgeWeightThreshold = value;
		}
	}

}

